// Copyright (c) 2021 Huawei Device Co., Ltd. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/viz/service/frame_sinks/external_begin_frame_source_ohos.h"
#include <cstdint>
#include <memory>

#include "base/bind.h"
#include "base/logging.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/trace_event/trace_event.h"
#include "ohos_adapter_helper.h"
#if BUILDFLAG(IS_OHOS)
#include "base/report_loss_frame.h"
#endif

namespace viz {
using namespace OHOS::NWeb;

constexpr int64_t VSYNC_PERIOD_90HZ = 11111111;
constexpr int64_t VSYNC_PERIOD_60HZ = 16666666;
constexpr int64_t VSYNC_PERIOD_6090HZ_MID = 13000000;
bool g_skip_vsync = false;
constexpr int64_t VSYNC_PERIOD_120HZ = 8333333;
constexpr int64_t VSYNC_PERIOD_90120HZ_MID = 9800000;

class ExternalBeginFrameSourceOHOS::VSyncUserData {
 public:
  VSyncUserData(const scoped_refptr<base::SingleThreadTaskRunner>& current,
                const base::WeakPtr<viz::ExternalBeginFrameSourceOHOS>& weakPtr)
      : current_(current), weak_ptr_(weakPtr) {
    LOG(INFO) << "VSyncUserData constructor!!!";
  };
  VSyncUserData(const VSyncUserData&) = delete;
  VSyncUserData& operator=(const VSyncUserData&) = delete;
  ~VSyncUserData() { LOG(INFO) << "VSyncUserData destructor!!!"; };

  const scoped_refptr<base::SingleThreadTaskRunner>& current_;
  base::WeakPtr<viz::ExternalBeginFrameSourceOHOS> weak_ptr_;
};

ExternalBeginFrameSourceOHOS::ExternalBeginFrameSourceOHOS(
    uint32_t restart_id,
    FrameSinkManagerImpl* frame_sink_manager)
    : ExternalBeginFrameSource(this, restart_id),
      vsync_notification_enabled_(false),
      first_vsync_since_notify_enabled_(false),
      vsync_adapter_(OhosAdapterHelper::GetInstance().GetVSyncAdapter()),
      frame_sink_manager_(frame_sink_manager) {
  TRACE_EVENT0("viz",
               "ExternalBeginFrameSourceOHOS::ExternalBeginFrameSourceOHOS");
  LOG(INFO) << "ExternalBeginFrameSourceOHOS constructor!!!";
  user_data_ = std::make_unique<VSyncUserData>(
      base::ThreadTaskRunnerHandle::Get(), weak_factory_.GetWeakPtr());
}

void ExternalBeginFrameSourceOHOS::SendInternalBeginFrame() {
  TRACE_EVENT0("viz", "ExternalBeginFrameSourceOHOS::SendInternalBeginFrame");
  base::TimeDelta vsync_period(base::Nanoseconds(vsync_period_));
  base::TimeTicks frame_time = base::TimeTicks::Now();
  auto begin_frame_args = begin_frame_args_generator_.GenerateBeginFrameArgs(
      source_id(), frame_time, last_dead_line_, vsync_period);

  begin_frame_args.internal_frame = true;
  OnBeginFrame(begin_frame_args);
}

ExternalBeginFrameSourceOHOS::~ExternalBeginFrameSourceOHOS() {
  LOG(INFO) << "ExternalBeginFrameSourceOHOS destructor!!!";
  SetEnabled(false);
}

void ExternalBeginFrameSourceOHOS::SetDynamicBeginFrameDeadlineOffsetSource(
    DynamicBeginFrameDeadlineOffsetSource*
        dynamic_begin_frame_deadline_offset_source) {
  TRACE_EVENT0(
      "viz",
      "ExternalBeginFrameSourceOHOS::SetDynamicBeginFrameDeadlineOffsetSource");
  begin_frame_args_generator_.set_dynamic_begin_frame_deadline_offset_source(
      dynamic_begin_frame_deadline_offset_source);
}

void ExternalBeginFrameSourceOHOS::OnVSync(int64_t timestamp, void* data) {
  if (data == nullptr) {
    LOG(ERROR) << "OnVSync data is nullptr";
    return;
  }
  VSyncUserData* userData = reinterpret_cast<VSyncUserData*>(data);
  if (!userData->current_) {
    LOG(ERROR) << "OnVSync data current is nullptr";
    return;
  }

  userData->current_->PostTask(
      FROM_HERE, base::BindOnce(&ExternalBeginFrameSourceOHOS::OnVSyncImpl,
                                userData->weak_ptr_, timestamp, userData));
}

void ExternalBeginFrameSourceOHOS::OnVSyncImpl(int64_t timestamp,
                                               VSyncUserData* user_data) {
  user_data_.reset(user_data);
  last_vsync_period_ = timestamp;
  vsync_period_ = vsync_adapter_.GetVSyncPeriod();
  if (vsync_period_ == 0) {
    if (first_vsync_since_notify_enabled_) {
      first_vsync_since_notify_enabled_ = false;
      pre_vsync_period_ = last_vsync_period_;
    } else {
      int64_t period = last_vsync_period_ - pre_vsync_period_;
      pre_vsync_period_ = last_vsync_period_;
      if (period > 0 && period < VSYNC_PERIOD_90120HZ_MID) {
        vsync_period_ = VSYNC_PERIOD_120HZ;
      } else if (period > 0 && period < VSYNC_PERIOD_6090HZ_MID) {
        vsync_period_ = VSYNC_PERIOD_90HZ;
      } else {
        vsync_period_ = VSYNC_PERIOD_60HZ;
      }
    }
  }
  if (lower_frame_rate_enabled_) {
    vsync_period_ = vsync_period_ * 2;
  }
  
  
#if BUILDFLAG(IS_OHOS)
ReportLossFrame::GetInstance()->SetVsyncPeriod(vsync_period_);
#endif

  base::TimeDelta vsync_period(base::Nanoseconds(vsync_period_));
  base::TimeTicks frame_time = base::TimeTicks() + base::Nanoseconds(timestamp);
  base::TimeTicks deadline = frame_time + vsync_period;
  last_dead_line_ = deadline;
  TRACE_EVENT2("viz", "ExternalBeginFrameSourceOHOS::OnVSyncImpl", "frame_time",
               frame_time, "deadline", deadline);
  
  if (lower_frame_rate_enabled_ && g_skip_vsync) {
    TRACE_EVENT0("viz", "vsync skip");
    g_skip_vsync = false;
  } else {
    TRACE_EVENT0("viz", "vsync not skip");
    auto begin_frame_args = begin_frame_args_generator_.GenerateBeginFrameArgs(
        source_id(), frame_time, deadline, vsync_period);
    OnBeginFrame(begin_frame_args);

    if (frame_sink_manager_) {
      TRACE_EVENT0("viz",
                    "ExternalBeginFrameSourceOHOS::OnVSyncImpl::BackToMainThread");
      frame_sink_manager_->OnVsync(frame_sink_id_);
    }
    g_skip_vsync = true;
  }

  if (!vsync_notification_enabled_ || user_data_ == nullptr) {
    return;
  }

  vsync_adapter_.RequestVsync(user_data_.release(),
                               ExternalBeginFrameSourceOHOS::OnVSync);
}

void ExternalBeginFrameSourceOHOS::OnNeedsBeginFrames(bool needs_begin_frames) {
  SetEnabled(needs_begin_frames);
}

void ExternalBeginFrameSourceOHOS::SetEnabled(bool enabled) {
  if (vsync_notification_enabled_ == enabled) {
    return;
  }
  TRACE_EVENT1("viz", "ExternalBeginFrameSourceOHOS::SetEnabled", "enabled",
               enabled);
  vsync_notification_enabled_ = enabled;
  first_vsync_since_notify_enabled_ = true;
  if (vsync_notification_enabled_ && user_data_ != nullptr) {
    vsync_adapter_.RequestVsync(user_data_.release(),
                                 ExternalBeginFrameSourceOHOS::OnVSync);
  }
}
}  // namespace viz
